home *** CD-ROM | disk | FTP | other *** search
/ PC World Plus! (NZ) 2001 June / HDC50.iso / Runimage / Delphi50 / Demos / Virtual Listview / vlistview.pas < prev   
Pascal/Delphi Source File  |  1999-08-11  |  17KB  |  664 lines

  1. unit VListView;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  7.   ComCtrls, StdCtrls, ToolWin, ShlObj, ImgList, Menus;
  8.  
  9. type
  10.   PShellItem = ^TShellItem;
  11.   TShellItem = record
  12.     FullID,
  13.     ID: PItemIDList;
  14.     Empty: Boolean;
  15.     DisplayName,
  16.     TypeName: string;
  17.     ImageIndex,
  18.     Size,
  19.     Attributes: Integer;
  20.     ModDate: string;
  21.   end;
  22.  
  23.   TForm1 = class(TForm)
  24.     ListView: TListView;
  25.     CoolBar1: TCoolBar;
  26.     ToolBar2: TToolBar;
  27.     ToolbarImages: TImageList;
  28.     btnBrowse: TToolButton;
  29.     btnLargeIcons: TToolButton;
  30.     btnSmallIcons: TToolButton;
  31.     btnList: TToolButton;
  32.     btnReport: TToolButton;
  33.     cbPath: TComboBox;
  34.     ToolButton3: TToolButton;
  35.     PopupMenu1: TPopupMenu;
  36.     btnBack: TToolButton;
  37.     procedure FormCreate(Sender: TObject);
  38.     procedure ListViewData(Sender: TObject; Item: TListItem);
  39.     procedure btnBrowseClick(Sender: TObject);
  40.     procedure cbPathKeyDown(Sender: TObject; var Key: Word;
  41.       Shift: TShiftState);
  42.     procedure cbPathClick(Sender: TObject);
  43.     procedure btnLargeIconsClick(Sender: TObject);
  44.     procedure ListViewDblClick(Sender: TObject);
  45.     procedure ListViewDataHint(Sender: TObject; StartIndex,
  46.       EndIndex: Integer);
  47.     procedure ListViewKeyDown(Sender: TObject; var Key: Word;
  48.       Shift: TShiftState);
  49.     procedure ListViewDataFind(Sender: TObject; Find: TItemFind;
  50.       const FindString: String; const FindPosition: TPoint;
  51.       FindData: Pointer; StartIndex: Integer; Direction: TSearchDirection;
  52.       Wrap: Boolean; var Index: Integer);
  53.     procedure ListViewCustomDrawItem(Sender: TCustomListView;
  54.       Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
  55.     procedure ListViewCustomDrawSubItem(Sender: TCustomListView;
  56.       Item: TListItem; SubItem: Integer; State: TCustomDrawState;
  57.       var DefaultDraw: Boolean);
  58.     procedure btnBackClick(Sender: TObject);
  59.     procedure Form1Close(Sender: TObject; var Action: TCloseAction);
  60.   private
  61.     FPIDL: PItemIDList;
  62.     FIDList: TList;
  63.     FIShellFolder,
  64.     FIDesktopFolder: IShellFolder;
  65.     FPath: string;
  66.     procedure SetPath(const Value: string); overload;
  67.     procedure SetPath(ID: PItemIDList); overload;
  68.     procedure PopulateIDList(ShellFolder: IShellFolder);
  69.     procedure ClearIDList;
  70.     procedure CheckShellItems(StartIndex, EndIndex: Integer);
  71.     function  ShellItem(Index: Integer): PShellItem;
  72.   end;
  73.  
  74. var
  75.   Form1: TForm1;
  76.  
  77. implementation
  78.  
  79. {$R *.DFM}
  80.  
  81. uses ShellAPI, ActiveX, ComObj, CommCtrl, FileCtrl;
  82.  
  83. //PIDL MANIPULATION
  84.  
  85. procedure DisposePIDL(ID: PItemIDList);
  86. var
  87.   Malloc: IMalloc;
  88. begin
  89.   if ID = nil then Exit;
  90.   OLECheck(SHGetMalloc(Malloc));
  91.   Malloc.Free(ID);
  92. end;
  93.  
  94. function CopyITEMID(Malloc: IMalloc; ID: PItemIDList): PItemIDList;
  95. begin
  96.   Result := Malloc.Alloc(ID^.mkid.cb + SizeOf(ID^.mkid.cb));
  97.   CopyMemory(Result, ID, ID^.mkid.cb + SizeOf(ID^.mkid.cb));
  98. end;
  99.  
  100. function NextPIDL(IDList: PItemIDList): PItemIDList;
  101. begin
  102.   Result := IDList;
  103.   Inc(PChar(Result), IDList^.mkid.cb);
  104. end;
  105.  
  106. function GetPIDLSize(IDList: PItemIDList): Integer;
  107. begin
  108.   Result := 0;
  109.   if Assigned(IDList) then
  110.   begin
  111.     Result := SizeOf(IDList^.mkid.cb);
  112.     while IDList^.mkid.cb <> 0 do
  113.     begin
  114.       Result := Result + IDList^.mkid.cb;
  115.       IDList := NextPIDL(IDList);
  116.     end;
  117.   end;
  118. end;
  119.  
  120.  
  121. procedure StripLastID(IDList: PItemIDList);
  122. var
  123.   MarkerID: PItemIDList;
  124. begin
  125.   MarkerID := IDList;
  126.   if Assigned(IDList) then
  127.   begin
  128.      while IDList.mkid.cb <> 0 do
  129.     begin
  130.       MarkerID := IDList;
  131.       IDList := NextPIDL(IDList);
  132.     end;
  133.     MarkerID.mkid.cb := 0;
  134.   end;
  135. end;
  136.  
  137. function CreatePIDL(Size: Integer): PItemIDList;
  138. var
  139.   Malloc: IMalloc;
  140.   HR: HResult;
  141. begin
  142.   Result := nil;
  143.  
  144.   HR := SHGetMalloc(Malloc);
  145.   if Failed(HR) then
  146.     Exit;
  147.  
  148.   try
  149.     Result := Malloc.Alloc(Size);
  150.     if Assigned(Result) then
  151.       FillChar(Result^, Size, 0);
  152.   finally
  153.   end;
  154. end;
  155.  
  156. function CopyPIDL(IDList: PItemIDList): PItemIDList;
  157. var
  158.   Size: Integer;
  159. begin
  160.   Size := GetPIDLSize(IDList);
  161.   Result := CreatePIDL(Size);
  162.   if Assigned(Result) then
  163.     CopyMemory(Result, IDList, Size);
  164. end;
  165.  
  166. function ConcatPIDLs(IDList1, IDList2: PItemIDList): PItemIDList;
  167. var
  168.   cb1, cb2: Integer;
  169. begin
  170.   if Assigned(IDList1) then
  171.     cb1 := GetPIDLSize(IDList1) - SizeOf(IDList1^.mkid.cb)
  172.   else
  173.     cb1 := 0;
  174.  
  175.   cb2 := GetPIDLSize(IDList2);
  176.  
  177.   Result := CreatePIDL(cb1 + cb2);
  178.   if Assigned(Result) then
  179.   begin
  180.     if Assigned(IDList1) then
  181.       CopyMemory(Result, IDList1, cb1);
  182.     CopyMemory(PChar(Result) + cb1, IDList2, cb2);
  183.   end;
  184. end;
  185.  
  186. //SHELL FOLDER ITEM INFO
  187.  
  188. function GetDisplayName(ShellFolder: IShellFolder; PIDL: PItemIDList;
  189.                         ForParsing: Boolean): string;
  190. var
  191.   StrRet: TStrRet;
  192.   P: PChar;
  193.   Flags: Integer;
  194. begin
  195.   Result := '';
  196.   if ForParsing then
  197.     Flags := SHGDN_FORPARSING
  198.   else
  199.     Flags := SHGDN_NORMAL;
  200.  
  201.   ShellFolder.GetDisplayNameOf(PIDL, Flags, StrRet);
  202.   case StrRet.uType of
  203.     STRRET_CSTR:
  204.       SetString(Result, StrRet.cStr, lStrLen(StrRet.cStr));
  205.     STRRET_OFFSET:
  206.       begin
  207.         P := @PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)];
  208.         SetString(Result, P, PIDL.mkid.cb - StrRet.uOffset);
  209.       end;
  210.     STRRET_WSTR:
  211.       Result := StrRet.pOleStr;
  212.   end;
  213. end;
  214.  
  215. function GetShellImage(PIDL: PItemIDList; Large, Open: Boolean): Integer;
  216. var
  217.   FileInfo: TSHFileInfo;
  218.   Flags: Integer;
  219. begin
  220.   FillChar(FileInfo, SizeOf(FileInfo), #0);
  221.   Flags := SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_ICON;
  222.   if Open then Flags := Flags or SHGFI_OPENICON;
  223.   if Large then Flags := Flags or SHGFI_LARGEICON
  224.   else Flags := Flags or SHGFI_SMALLICON;
  225.   SHGetFileInfo(PChar(PIDL),
  226.                 0,
  227.                 FileInfo,
  228.                 SizeOf(FileInfo),
  229.                 Flags);
  230.   Result := FileInfo.iIcon;
  231. end;
  232.  
  233. function IsFolder(ShellFolder: IShellFolder; ID: PItemIDList): Boolean;
  234. var
  235.   Flags: UINT;
  236. begin
  237.   Flags := SFGAO_FOLDER;
  238.   ShellFolder.GetAttributesOf(1, ID, Flags);
  239.   Result := SFGAO_FOLDER and Flags <> 0;
  240. end;
  241.  
  242.  
  243. function ListSortFunc(Item1, Item2: Pointer): Integer;
  244. begin
  245.   Result := SmallInt(Form1.FIShellFolder.CompareIDs(
  246.                   0,
  247.                   PShellItem(Item1).ID,
  248.                   PShellItem(Item2).ID
  249.             ));
  250. end;
  251.  
  252. {TForm1}
  253.  
  254. //GENERAL FORM METHODS
  255.  
  256. procedure TForm1.FormCreate(Sender: TObject);
  257. var
  258.   FileInfo: TSHFileInfo;
  259.   ImageListHandle: THandle;
  260.   NewPIDL: PItemIDList;
  261. begin
  262.   OLECheck(SHGetDesktopFolder(FIDesktopFolder));
  263.   FIShellFolder := FIDesktopFolder;
  264.   FIDList := TList.Create;
  265.   ImageListHandle := SHGetFileInfo('C:\',
  266.                            0,
  267.                            FileInfo,
  268.                            SizeOf(FileInfo),
  269.                            SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  270.   SendMessage(ListView.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, ImageListHandle);
  271.  
  272.   ImageListHandle := SHGetFileInfo('C:\',
  273.                            0,
  274.                            FileInfo,
  275.                            SizeOf(FileInfo),
  276.                            SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
  277.  
  278.   SendMessage(ListView.Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, ImageListHandle);
  279.   OLECheck(
  280.     SHGetSpecialFolderLocation(
  281.       Application.Handle,
  282.       CSIDL_DRIVES,
  283.       NewPIDL)
  284.   );
  285.   SetPath(NewPIDL);
  286.   ActiveControl := cbPath;
  287.   cbPath.SelStart := 0;
  288.   cbPath.SelLength := Length(cbPath.Text);
  289. end;
  290.  
  291. procedure TForm1.btnBrowseClick(Sender: TObject);
  292. var
  293.   S: string;
  294. begin
  295.   S := '';
  296.   if SelectDirectory('Select Directory', '', S) then
  297.     SetPath(S);
  298. end;
  299.  
  300. procedure TForm1.cbPathKeyDown(Sender: TObject; var Key: Word;
  301.   Shift: TShiftState);
  302. begin
  303.   if Key = VK_RETURN then
  304.   begin
  305.     if cbPath.Text[Length(cbPath.Text)] = ':' then
  306.       cbPath.Text := cbPath.Text + '\'; 
  307.     SetPath(cbPath.Text);
  308.     Key := 0;
  309.   end;
  310. end;
  311.  
  312. procedure TForm1.cbPathClick(Sender: TObject);
  313. var
  314.   I: Integer;
  315. begin
  316.   I := cbPath.Items.IndexOf(cbPath.Text);
  317.   if I >= 0 then
  318.     SetPath(PItemIDList(cbPath.Items.Objects[I]))
  319.   else
  320.     SetPath(cbPath.Text);
  321. end;
  322.  
  323. procedure TForm1.btnLargeIconsClick(Sender: TObject);
  324. begin
  325.   ListView.ViewStyle := TViewStyle((Sender as TComponent).Tag);
  326. end;
  327.  
  328. procedure TForm1.ListViewDblClick(Sender: TObject);
  329. var
  330.   RootPIDL,
  331.   ID: PItemIDList;
  332. begin
  333.   if ListView.Selected <> nil then
  334.   begin
  335.     ID := ShellItem(ListView.Selected.Index).ID;
  336.     if not IsFolder(FIShellFolder, ID) then Exit;
  337.     RootPIDL := ConcatPIDLs(FPIDL, ID);
  338.     SetPath(RootPIDL);
  339.   end;
  340. end;
  341.  
  342. function TForm1.ShellItem(Index: Integer): PShellItem;
  343. begin
  344.   Result := PShellItem(FIDList[Index]);
  345. end;
  346.  
  347. procedure TForm1.ListViewKeyDown(Sender: TObject; var Key: Word;
  348.   Shift: TShiftState);
  349. begin
  350.   case Key of
  351.     VK_RETURN:
  352.       ListViewDblClick(Sender);
  353.     VK_BACK:
  354.       btnBackClick(Sender);  
  355.   end;
  356. end;
  357.  
  358. //SHELL-RELATED ROUTINES.
  359.  
  360. procedure TForm1.ClearIDList;
  361. var
  362.   I: Integer;
  363. begin
  364.   for I := 0 to FIDList.Count-1 do
  365.   begin
  366.     DisposePIDL(ShellItem(I).ID);
  367.     Dispose(ShellItem(I));
  368.   end;
  369.   FIDList.Clear;
  370. end;
  371.  
  372. procedure TForm1.PopulateIDList(ShellFolder: IShellFolder);
  373. const
  374.   Flags = SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN;
  375. var
  376.   ID: PItemIDList;
  377.   EnumList: IEnumIDList;
  378.   NumIDs: LongWord;
  379.   SaveCursor: TCursor;
  380.   ShellItem: PShellItem;
  381. begin
  382.   SaveCursor := Screen.Cursor;
  383.   try
  384.     Screen.Cursor := crHourglass;
  385.     OleCheck(
  386.       ShellFolder.EnumObjects(
  387.         Application.Handle,
  388.         Flags,
  389.         EnumList)
  390.     );
  391.  
  392.     FIShellFolder := ShellFolder;
  393.     ClearIDList;
  394.     while EnumList.Next(1, ID, NumIDs) = S_OK do
  395.     begin
  396.       ShellItem := New(PShellItem);
  397.       ShellItem.ID := ID;
  398.       ShellItem.DisplayName := GetDisplayName(FIShellFolder, ID, False);
  399.       ShellItem.Empty := True;
  400.       FIDList.Add(ShellItem);
  401.     end;
  402.  
  403.     FIDList.Sort(ListSortFunc);
  404.  
  405.     //We need to tell the ListView how many items it has.
  406.     ListView.Items.Count := FIDList.Count;
  407.  
  408.     ListView.Repaint;
  409.   finally
  410.     Screen.Cursor := SaveCursor;
  411.   end;
  412. end;
  413.  
  414. procedure TForm1.SetPath(const Value: string);
  415. var
  416.   P: PWideChar;
  417.   NewPIDL: PItemIDList;
  418.   Flags,
  419.   NumChars: LongWord;
  420. begin
  421.   NumChars := Length(Value);
  422.   Flags := 0;
  423.   P := StringToOleStr(Value);
  424.  
  425.   OLECheck(
  426.     FIDesktopFolder.ParseDisplayName(
  427.       Application.Handle,
  428.       nil,
  429.       P,
  430.       NumChars,
  431.       NewPIDL,
  432.       Flags)
  433.    );
  434.   SetPath(NewPIDL);
  435. end;
  436.  
  437. procedure TForm1.SetPath(ID: PItemIDList);
  438. var
  439.   Index: Integer;
  440.   NewShellFolder: IShellFolder;
  441. begin
  442.    OLECheck(
  443.      FIDesktopFolder.BindToObject(
  444.             ID,
  445.             nil,
  446.             IID_IShellFolder,
  447.             Pointer(NewShellFolder))
  448.    );
  449.  
  450.   ListView.Items.BeginUpdate;
  451.   try
  452.     PopulateIDList(NewShellFolder);
  453.     FPIDL := ID;
  454.     FPath := GetDisplayName(FIDesktopFolder, FPIDL, True);
  455.     Index := cbPath.Items.IndexOf(FPath);
  456.     if (Index < 0) then
  457.     begin
  458.       cbPath.Items.InsertObject(0, FPath, Pointer(FPIDL));
  459.       cbPath.Text := cbPath.Items[0];
  460.     end
  461.     else begin
  462.       cbPath.ItemIndex := Index;
  463.       cbPath.Text := cbPath.Items[cbPath.ItemIndex];
  464.     end;
  465.  
  466.     if ListView.Items.Count > 0 then
  467.     begin
  468.       ListView.Selected := ListView.Items[0];
  469.       ListView.Selected.Focused := True;
  470.       ListView.Selected.MakeVisible(False);
  471.     end;
  472.   finally
  473.     ListView.Items.EndUpdate;
  474.   end;
  475. end;
  476.  
  477. //ROUTINES FOR MANAGING VIRTUAL DATA
  478.  
  479. procedure TForm1.CheckShellItems(StartIndex, EndIndex: Integer);
  480.  
  481.  function ValidFileTime(FileTime: TFileTime): Boolean;
  482.  begin
  483.    Result := (FileTime.dwLowDateTime <> 0) or (FileTime.dwHighDateTime <> 0);
  484.  end;
  485.  
  486. var
  487.   FileData: TWin32FindData;
  488.   FileInfo: TSHFileInfo;
  489.   SysTime: TSystemTime;
  490.   I: Integer;
  491.   LocalFileTime: TFILETIME;
  492. begin
  493.   //Here all the data that wasn't initialized in PopulateIDList is
  494.   //filled in.
  495.   for I := StartIndex to EndIndex do
  496.   begin
  497.     if ShellItem(I)^.Empty then
  498.     with ShellItem(I)^ do
  499.     begin
  500.       FullID := ConcatPIDLs(FPIDL, ID);
  501.       ImageIndex := GetShellImage(FullID, ListView.ViewStyle = vsIcon, False);
  502.  
  503.       //File Type
  504.       SHGetFileInfo(
  505.         PChar(FullID),
  506.         0,
  507.         FileInfo,
  508.         SizeOf(FileInfo),
  509.         SHGFI_TYPENAME or SHGFI_PIDL
  510.       );
  511.       TypeName := FileInfo.szTypeName;
  512.  
  513.       //Get File info from Windows
  514.       FillChar(FileData, SizeOf(FileData), #0);
  515.       SHGetDataFromIDList(
  516.         FIShellFolder,
  517.         ID,
  518.         SHGDFIL_FINDDATA,
  519.         @FileData,
  520.         SizeOf(FileData)
  521.       );
  522.  
  523.       //File Size, in KB
  524.       Size := FileData.nFileSizeLow div 1000;
  525.       if Size = 0 then Size := 1;
  526.  
  527.       //Modified Date
  528.       FillChar(LocalFileTime, SizeOf(TFileTime), #0);
  529.       with FileData do
  530.         if ValidFileTime(ftLastWriteTime)
  531.         and FileTimeToLocalFileTime(ftLastWriteTime, LocalFileTime)
  532.         and FileTimeToSystemTime(LocalFileTime, SysTime) then
  533.         try
  534.           ModDate := DateTimeToStr(SystemTimeToDateTime(SysTime))
  535.         except
  536.           on EConvertError do ModDate := '';
  537.         end
  538.         else
  539.           ModDate := '';
  540.  
  541.       //Attributes
  542.       Attributes := FileData.dwFileAttributes;
  543.  
  544.       //Flag this record as complete.
  545.       Empty := False;
  546.     end;
  547.   end;
  548. end;
  549.  
  550. procedure TForm1.ListViewDataHint(Sender: TObject; StartIndex,
  551.   EndIndex: Integer);
  552. begin
  553.   //OnDataHint is called before OnData. This gives you a chance to
  554.   //initialize only the data structures that need to be drawn.
  555.   //You should keep track of which items have been initialized so no
  556.   //extra work is done.
  557.   if (StartIndex > FIDList.Count) or (EndIndex > FIDList.Count) then Exit;
  558.   CheckShellItems(StartIndex, EndIndex);
  559. end;
  560.  
  561. procedure TForm1.ListViewData(Sender: TObject; Item: TListItem);
  562. var
  563.   Attrs: string;
  564. begin
  565.   //OnData gets called once for each item for which the ListView needs
  566.   //data. If the ListView is in Report View, be sure to add the subitems.
  567.   //Item is a "dummy" item whose only valid data is it's index which
  568.   //is used to index into the underlying data.
  569.   if (Item.Index > FIDList.Count) then Exit;
  570.   with ShellItem(Item.Index)^ do
  571.   begin
  572.     Item.Caption := DisplayName;
  573.     Item.ImageIndex := ImageIndex;
  574.  
  575.     if ListView.ViewStyle <> vsReport then Exit;
  576.  
  577.     if not IsFolder(FIShellFolder, ID) then
  578.       Item.SubItems.Add(Format('%dKB', [Size]))
  579.     else
  580.       Item.SubItems.Add('');
  581.     Item.SubItems.Add(TypeName);
  582.     try
  583.       Item.SubItems.Add(ModDate);
  584.     except
  585.     end;
  586.  
  587.     if Bool(Attributes and FILE_ATTRIBUTE_READONLY) then Attrs := Attrs + 'R';
  588.     if Bool(Attributes and FILE_ATTRIBUTE_HIDDEN) then Attrs := Attrs + 'H';
  589.     if Bool(Attributes and FILE_ATTRIBUTE_SYSTEM) then Attrs := Attrs + 'S';
  590.     if Bool(Attributes and FILE_ATTRIBUTE_ARCHIVE) then Attrs := Attrs + 'A';
  591.   end;
  592.   Item.SubItems.Add(Attrs);
  593. end;
  594.  
  595. procedure TForm1.ListViewDataFind(Sender: TObject; Find: TItemFind;
  596.   const FindString: String; const FindPosition: TPoint; FindData: Pointer;
  597.   StartIndex: Integer; Direction: TSearchDirection; Wrap: Boolean;
  598.   var Index: Integer);
  599. //OnDataFind gets called in response to calls to FindCaption, FindData,
  600. //GetNearestItem, etc. It also gets called for each keystroke sent to the
  601. //ListView (for incremental searching)
  602. var
  603.   I: Integer;
  604.   Found: Boolean;
  605. begin
  606.   I := StartIndex;
  607.   if (Find = ifExactString) or (Find = ifPartialString) then
  608.   begin
  609.     repeat
  610.       if (I = FIDList.Count-1) then
  611.         if Wrap then I := 0 else Exit;
  612.       Found := Pos(UpperCase(FindString), UpperCase(ShellItem(I)^.DisplayName)) = 1;
  613.       Inc(I);
  614.     until Found or (I = StartIndex);
  615.     if Found then Index := I-1;
  616.   end;
  617. end;
  618.  
  619. procedure TForm1.ListViewCustomDrawItem(Sender: TCustomListView;
  620.   Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
  621. var
  622.   Attrs: Integer;
  623. begin
  624.   if Item = nil then Exit;
  625.   Attrs := ShellItem(Item.Index).Attributes;
  626.   if Bool(Attrs and FILE_ATTRIBUTE_READONLY) then
  627.     ListView.Canvas.Font.Color := clGrayText;
  628.   if Bool(Attrs and FILE_ATTRIBUTE_HIDDEN) then
  629.     ListView.Canvas.Font.Style :=
  630.        ListView.Canvas.Font.Style + [fsStrikeOut];
  631.   if Bool(Attrs and FILE_ATTRIBUTE_SYSTEM) then
  632.     Listview.Canvas.Font.Color := clHighlight;
  633. end;
  634.  
  635. procedure TForm1.ListViewCustomDrawSubItem(Sender: TCustomListView;
  636.   Item: TListItem; SubItem: Integer; State: TCustomDrawState;
  637.   var DefaultDraw: Boolean);
  638. begin
  639.   if SubItem = 0 then Exit;
  640.   ListView.Canvas.Font.Color := GetSysColor(COLOR_WINDOWTEXT);
  641.   //workaround for Win98 bug.
  642. end;
  643.  
  644. procedure TForm1.btnBackClick(Sender: TObject);
  645. var
  646.   Temp: PItemIDList;
  647. begin
  648.   Temp := CopyPIDL(FPIDL);
  649.   if Assigned(Temp) then
  650.     StripLastID(Temp);
  651.   if Temp.mkid.cb <> 0 then
  652.     SetPath(Temp)
  653.   else
  654.     Beep;
  655. end;
  656.  
  657. procedure TForm1.Form1Close(Sender: TObject; var Action: TCloseAction);
  658. begin
  659.   ClearIDList;
  660.   FIDList.Free;
  661. end;
  662.  
  663. end.
  664.